#include "mainwindow.h"

#include <QSound>
#include <QSettings>
#include <QToolBar>
#include <QLCDNumber>
#include <QGridLayout>
#include <QCloseEvent>
#include <QDialog>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QFile>

#include "CellWidget.h"

static QMap<CellWidget::Dirs, CellWidget::Dirs> contrdirs;

MainWindow::MainWindow(QWidget *parent) :
	QMainWindow(parent, Qt::Dialog) {

	ui.setupUi(this);

	contrdirs[CellWidget::U] = CellWidget::D;
	contrdirs[CellWidget::R] = CellWidget::L;
	contrdirs[CellWidget::D] = CellWidget::U;
	contrdirs[CellWidget::L] = CellWidget::R;

	QString appDir = qApp->applicationDirPath();
	QString sndDir = appDir + "/resource/sounds/";

	if (!QFile::exists(sndDir)) {
		sndDir = appDir + "/../resource/sounds/";
	}

	winSound = new QSound(sndDir + "win.wav");
	turnSound = new QSound(sndDir + "turn.wav");
	clickSound = new QSound(sndDir + "click.wav");
	startSound = new QSound(sndDir + "start.wav");
	connectSound = new QSound(sndDir + "connect.wav");

	QSettings settings("QtNetWalk");
	user = settings.value("Username", getenv("USER")).toString();
	bool isSound = settings.value("Sound", true).toBool();

	highscores = settings.value("Highscores").toStringList();
	if (highscores.count() != NumHighscores * 8) {

		highscores.clear();

		for (int i = 1; i < 5; i++) {
			for (int scores = 20 * i; scores < 30 * i; scores += i) {

				highscores.append("...");
				highscores.append(QString::number(scores));
			}
		}
	}

	skill = settings.value("Skill", Novice).toInt();
	if ((skill != Novice) && (skill != Normal) && (skill != Expert)) {
		skill = Master;
	}
	for (int i = 1; i < qApp->argc(); i++) {

		QString argument = qApp->argv()[i];
		if (argument == "-novice")
			skill = Novice;
		else if (argument == "-amateur")
			skill = Normal;
		else if (argument == "-expert")
			skill = Expert;
		else if (argument == "-master")
			skill = Master;
		else if (argument == "-nosound")
			isSound = false;
		else
			qWarning(
					QString("Unknown option: '" + argument + "'. Try -help.").toAscii());
	}

	setWindowTitle(tr("QtNetWalk"));
	setWindowIcon(QPixmap(":/pics/resource/pics/qnetwalk.png"));
	//	setFixedSize(minimumSizeHint());
	setFocusPolicy(Qt::StrongFocus);

	QMenu * gameMenu = new QMenu(tr("&Game"), this);
	QToolBar * toolbar = addToolBar("Toolbar");
	toolbar->setMovable(false);

	QAction * action;
	action = gameMenu->addAction(QPixmap(":/pics/resource/pics/newgame.png"),
			tr("&New"), this, SLOT(newGame()), QKeySequence("N"));
	toolbar->addAction(action);

	action = gameMenu->addAction(
			QPixmap(":/pics/resource/pics/highscores.png"), tr("&Highscores"),
			this, SLOT(showHighscores()), QKeySequence("H"));
	toolbar->addAction(action);

	soundAction = gameMenu->addAction(tr("&Sound"));
	soundAction->setShortcut(QKeySequence("S"));
	soundAction->setCheckable(true);
	soundAction->setChecked(isSound);

	gameMenu->addSeparator();
	gameMenu->addAction(QPixmap(":/pics/resource/pics/quit.png"), tr("&Quit"),
			qApp, SLOT(closeAllWindows()), QKeySequence("Q"));

	QActionGroup * actions = new QActionGroup(this);
	connect(actions, SIGNAL(triggered(QAction *)),
			SLOT(triggeredSkill(QAction *)));

	action = actions->addAction(tr("&Novice"));
	action->setShortcut(QKeySequence("1"));
	action->setData(Novice);

	action = actions->addAction(tr("&Amateur"));
	action->setShortcut(QKeySequence("2"));
	action->setData(Normal);

	action = actions->addAction(tr("&Expert"));
	action->setShortcut(QKeySequence("3"));
	action->setData(Expert);

	action = actions->addAction(tr("&Master"));
	action->setShortcut(QKeySequence("4"));
	action->setData(Master);

	foreach(QAction * a, actions->actions())
		{
			a->setCheckable(true);
			if (a->data() == skill)
				a->setChecked(true);
		}

	QMenu * skillMenu = new QMenu(tr("&Skill"), this);
	skillMenu->addActions(actions->actions());

	QMenu * helpMenu = new QMenu(tr("&Help"), this);

	helpMenu->addAction(tr("&Rules of Play"), this, SLOT(help()), QKeySequence(
			"F1"));
	helpMenu->addAction(QPixmap(":/pics/resource/pics/homepage.png"), tr(
			"&Homepage"), this, SLOT(openHomepage()));
	helpMenu->addAction(tr("&Keyboard"), this, SLOT(keyboard()), QKeySequence(
			"K"));
	helpMenu->addSeparator();
	helpMenu->addAction(QPixmap(":/pics/resource/pics/qnetwalk.png"), tr(
			"&About QNetWalk"), this, SLOT(about()));
	helpMenu->addAction(tr("About &Qt"), qApp, SLOT(aboutQt()));

	menuBar()->addMenu(gameMenu);
	menuBar()->addMenu(skillMenu);
	menuBar()->addMenu(helpMenu);

	lcd = new QLCDNumber(toolbar);
	lcd->setFrameStyle(QFrame::Plain);
	toolbar->addWidget(lcd);

	const int cellsize = QPixmap(":/pics/resource/pics/background.png").width();
	const int gridsize = cellsize * MasterBoardSize + 2;

	QGridLayout * grid = new QGridLayout;
	grid->setMargin(0);
	grid->setSpacing(0);

	QFrame* frame = new QFrame(this);
	frame->setFrameStyle(QFrame::Panel | QFrame::Sunken);
	frame->setFixedSize(gridsize, gridsize);
	frame->setLayout(grid);
	setCentralWidget(frame);

	CellWidget::initPixmaps();
	for (int i = 0; i < MasterBoardSize * MasterBoardSize; i++) {

		board[i] = new CellWidget(i, frame);
		board[i]->setFixedSize(cellsize, cellsize);

		connect(board[i], SIGNAL(noRotation()), SLOT(noRotation()));
		connect(board[i], SIGNAL(startRotation()), SLOT(startRotation()));
		connect(board[i], SIGNAL(finishRotation()), SLOT(finishRotation()));

		grid->addWidget(board[i], i / MasterBoardSize, i % MasterBoardSize);
	}

	for (int y = 0; y < MasterBoardSize; y++) {
		for (int x = 0; x < MasterBoardSize; x++) {

			const int i = MasterBoardSize * y + x;
			if (y == 0) {
				if (x == 0)
					board[i]->setShadow(CellWidget::TopLeftShadow);
				else if (x == MasterBoardSize - 1)
					board[i]->setShadow(CellWidget::TopRightShadow);
				else
					board[i]->setShadow(CellWidget::TopShadow);
			} else if (y == MasterBoardSize - 1) {
				if (x == 0)
					board[i]->setShadow(CellWidget::BottomLeftShadow);
				else if (x == MasterBoardSize - 1)
					board[i]->setShadow(CellWidget::BottomRightShadow);
				else
					board[i]->setShadow(CellWidget::BottomShadow);
			} else if (x == 0)
				board[i]->setShadow(CellWidget::LeftShadow);
			else if (x == MasterBoardSize - 1)
				board[i]->setShadow(CellWidget::RightShadow);
			else
				board[i]->setShadow(CellWidget::NoShadow);
		}
	}

	srand(time(0));
	setSkill(skill);

	setFixedSize(minimumSizeHint());
}

MainWindow::~MainWindow() {

}

void MainWindow::setSkill(int s) {

	skill = s;

	if (skill == Master)
		wrapped = true;
	else
		wrapped = false;

	newGame();
}

void MainWindow::newGame() {

	lcd->display(0);
	if (soundAction->isChecked())
		startSound->play();

	for (int i = 0; i < MasterBoardSize * MasterBoardSize; i++) {

		board[i]->setDirs(CellWidget::None);
		board[i]->setConnected(false);
		board[i]->setLocked(false);
		board[i]->setRoot(false);
	}

	const int size = (skill == Novice) ? NoviceBoardSize
			: (skill == Normal) ? NormalBoardSize
					: (skill == Expert) ? ExpertBoardSize : MasterBoardSize;

	const int start = (MasterBoardSize - size) / 2;
	const int rootrow = rand() % size;
	const int rootcol = rand() % size;

	root = board[(start + rootrow) * MasterBoardSize + start + rootcol];
	root->setConnected(true);
	root->setRoot(true);

	while (true) {

		for (int row = start; row < start + size; row++)
			for (int col = start; col < start + size; col++)
				board[row * MasterBoardSize + col]->setDirs(CellWidget::Free);

		CellList list;
		list.append(root);
		if (rand() % 2)
			addRandomDir(list);

		while (!list.isEmpty()) {

			if (rand() % 2) {

				addRandomDir(list);
				if (rand() % 2)
					addRandomDir(list);
				list.removeFirst();
			} else {
				list.append(list.first());
				list.removeFirst();
			}
		}

		int cells = 0;
		for (int i = 0; i < MasterBoardSize * MasterBoardSize; i++) {
			CellWidget::Dirs d = board[i]->dirs();
			if ((d != CellWidget::Free) && (d != CellWidget::None))
				cells++;
		}
		if (cells >= MinimumNumCells)
			break;
	}

	CellWidget::setGameOver(false);
	CellWidget::setFocused(0);
	for (int i = 0; i < MasterBoardSize * MasterBoardSize; i++)
		board[i]->rotate((rand() % 4) * 90);

	updateConnections();
}

void MainWindow::addRandomDir(CellList& list) {

	CellWidget * cell = list.first();
	CellWidget * ucell = uCell(cell, wrapped);
	CellWidget * rcell = rCell(cell, wrapped);
	CellWidget * dcell = dCell(cell, wrapped);
	CellWidget * lcell = lCell(cell, wrapped);

	typedef QMap<CellWidget::Dirs, CellWidget *> CellMap;
	CellMap freecells;

	if (ucell && ucell->dirs() == CellWidget::Free)
		freecells[CellWidget::U] = ucell;
	if (rcell && rcell->dirs() == CellWidget::Free)
		freecells[CellWidget::R] = rcell;
	if (dcell && dcell->dirs() == CellWidget::Free)
		freecells[CellWidget::D] = dcell;
	if (lcell && lcell->dirs() == CellWidget::Free)
		freecells[CellWidget::L] = lcell;
	if (freecells.isEmpty())
		return;

	CellMap::ConstIterator it = freecells.constBegin();
	for (int i = rand() % freecells.count(); i > 0; --i)
		++it;

	cell->setDirs(CellWidget::Dirs(cell->dirs() | it.key()));
	it.value()->setDirs(contrdirs[it.key()]);
	list.append(it.value());
}

CellWidget * MainWindow::uCell(CellWidget * cell, bool wrap) const {

	if (cell->index() >= MasterBoardSize)
		return board[cell->index() - MasterBoardSize];
	else if (wrap)
		return board[MasterBoardSize * (MasterBoardSize - 1) + cell->index()];
	else
		return 0;
}

CellWidget * MainWindow::dCell(CellWidget * cell, bool wrap) const {
	if (cell->index() < MasterBoardSize * (MasterBoardSize - 1))
		return board[cell->index() + MasterBoardSize];
	else if (wrap)
		return board[cell->index() - MasterBoardSize * (MasterBoardSize - 1)];
	else
		return 0;
}

CellWidget * MainWindow::lCell(CellWidget * cell, bool wrap) const {
	if (cell->index() % MasterBoardSize > 0)
		return board[cell->index() - 1];
	else if (wrap)
		return board[cell->index() - 1 + MasterBoardSize];
	else
		return 0;
}

CellWidget * MainWindow::rCell(CellWidget * cell, bool wrap) const {
	if (cell->index() % MasterBoardSize < MasterBoardSize - 1)
		return board[cell->index() + 1];
	else if (wrap)
		return board[cell->index() + 1 - MasterBoardSize];
	else
		return 0;
}

bool MainWindow::updateConnections() {

	bool newconnection[MasterBoardSize * MasterBoardSize];
	for (int i = 0; i < MasterBoardSize * MasterBoardSize; i++)
		newconnection[i] = false;

	CellList list;
	if (!root->isRotated()) {
		newconnection[root->index()] = true;
		list.append(root);
	}

	while (!list.isEmpty()) {

		CellWidget* cell = list.first();
		CellWidget* ucell = uCell(cell, wrapped);
		CellWidget* rcell = rCell(cell, wrapped);
		CellWidget* dcell = dCell(cell, wrapped);
		CellWidget* lcell = lCell(cell, wrapped);

		if ((cell->dirs() & CellWidget::U) && ucell && (ucell->dirs()
				& CellWidget::D) && !newconnection[ucell->index()]
				&& !ucell->isRotated()) {

			newconnection[ucell->index()] = true;
			list.append(ucell);
		}
		if ((cell->dirs() & CellWidget::R) && rcell && (rcell->dirs()
				& CellWidget::L) && !newconnection[rcell->index()]
				&& !rcell->isRotated()) {

			newconnection[rcell->index()] = true;
			list.append(rcell);
		}
		if ((cell->dirs() & CellWidget::D) && dcell && (dcell->dirs()
				& CellWidget::U) && !newconnection[dcell->index()]
				&& !dcell->isRotated()) {

			newconnection[dcell->index()] = true;
			list.append(dcell);
		}
		if ((cell->dirs() & CellWidget::L) && lcell && (lcell->dirs()
				& CellWidget::R) && !newconnection[lcell->index()]
				&& !lcell->isRotated()) {

			newconnection[lcell->index()] = true;
			list.append(lcell);
		}
		list.removeFirst();
	}

	bool isnewconnection = false;
	for (int i = 0; i < MasterBoardSize * MasterBoardSize; i++) {

		if (!board[i]->isConnected() && newconnection[i])
			isnewconnection = true;
		board[i]->setConnected(newconnection[i]);
	}

	return isnewconnection;
}

bool MainWindow::isGameOver() {

	for (int i = 0; i < MasterBoardSize * MasterBoardSize; i++) {

		const CellWidget::Dirs d = board[i]->dirs();
		if ((d != CellWidget::Free) && (d != CellWidget::None)
				&& !board[i]->isConnected())
			return false;
	}

	for (int i = 0; i < MasterBoardSize * MasterBoardSize; i++)
		board[i]->setLocked(true);

	CellWidget::setGameOver(true);
	CellWidget::setFocused(0);

	return true;
}

void MainWindow::closeEvent(QCloseEvent * event) {

	QSettings settings("QtNetWalk");
	settings.setValue("Skill", skill);
	settings.setValue("Username", user);
	settings.setValue("Highscores", highscores);
	settings.setValue("Sound", soundAction->isChecked());
	event->accept();
}

void MainWindow::triggeredSkill(QAction * action) {
	setSkill(action->data().toInt());
}

void MainWindow::noRotation() {
	if (soundAction->isChecked())
		clickSound->play();
}

void MainWindow::startRotation() {
	if (soundAction->isChecked())
		turnSound->play();

	updateConnections();
}

void MainWindow::finishRotation() {

	if (updateConnections() && soundAction->isChecked())
		connectSound->play();

	lcd->display(lcd->intValue() + 1);
	if (isGameOver()) {

		if (soundAction->isChecked())
			winSound->play();

		QString score = highscores.at(2 * (skill + 1) * NumHighscores - 1);
		if (lcd->intValue() <= score.toInt())
			addHighscore(lcd->intValue());
	}
}

void MainWindow::addHighscore(int score) {
	QDialog * dialog = new QDialog(this);
	dialog->setWindowTitle(tr("Highscores"));

	QGridLayout * grid = new QGridLayout(dialog);
	grid->setMargin(10);
	grid->setSpacing(5);
	grid->setColumnStretch(2, 1);
	grid->setRowStretch(NumHighscores + 3, 1);
	grid->setColumnMinimumWidth(0, 40);
	grid->setColumnMinimumWidth(2, 150);

	QLabel * label = new QLabel(dialog);
	label->setPixmap(QPixmap(":/pics/resource/pics/computer2.png"));
	grid->addWidget(label, 0, 0, 2, 1);
	label = new QLabel(QString("<h3>") + dialog->windowTitle(), dialog);
	grid->addWidget(label, 0, 1, 1, 3);

	const QString header = (skill == Novice) ? tr("Novices")
			: (skill == Normal) ? tr("Amateurs") : (skill == Expert) ? tr(
					"Experts") : tr("Masters");

	grid->addWidget(new QLabel("<b>#", dialog), 1, 1);
	grid->addWidget(new QLabel("<b>" + header, dialog), 1, 2);
	grid->addWidget(new QLabel("<b>" + tr("Scores"), dialog), 1, 3);

	QFrame * frame = new QFrame(dialog);
	frame->setFrameStyle(QFrame::HLine | QFrame::Sunken);
	grid->addWidget(frame, 2, 1, 1, 3);

	QLineEdit * line = 0;
	int inserted = 0;
	int index = 2 * skill * NumHighscores;
	for (unsigned int i = 0; i < NumHighscores; i++) {
		label = new QLabel(QString::number(i + 1), dialog);
		grid->addWidget(label, i + 3, 1);

		int next = index;
		if ((score > 0) && (score <= highscores.at(++next).toInt()) && !line) {
			inserted = index;
			line = new QLineEdit(user, dialog);
			grid->addWidget(line, i + 3, 2);
			label = new QLabel(QString::number(score), dialog);
			grid->addWidget(label, i + 3, 3);
		} else {
			grid->addWidget(new QLabel(highscores.at(index++), dialog), i + 3,
					2);
			grid->addWidget(new QLabel(highscores.at(index++), dialog), i + 3,
					3);
		}
	}
	QPushButton * button = new QPushButton("OK", dialog);
	connect(button, SIGNAL(clicked()), dialog, SLOT(accept()));
	const int pos = NumHighscores + 4;
	grid->addWidget(button, pos, 0, 1, 4, Qt::AlignHCenter);
	dialog->exec();

	if (line) {
		user = line->text();
		highscores.insert(inserted, QString::number(score));
		highscores.insert(inserted, user);
		highscores.removeAt(index);
		highscores.removeAt(index);
	}
	delete dialog;
}

void MainWindow::showHighscores() {
	addHighscore(0);
}
